package com.jianxin.spring;

import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 模拟spring容器
 *
 * jdk动态代理就是通过实现目标类的接口然后可以在里面做自己的增强之后调用目标类的方法
 * cglib代理通过继承目标类，同时持有目标类对象,继承目标类是为了可以强制转换编译通过,持有目标类是为了可以调用目标对象本身的方法，所有在spring的事务失效场景中为什么会事务失效就是因为调用的是目标类本身的方法而非代理类的方法
 * spring事务失效的各种场景可以去网上看看
 *
 * beanDefinition 就是我们理解的bean对象
 * spring的BeanDefinition容器是个map key是唯一 value可以重复 所以spring优先是先通过类型筛选找不到则再通过名称筛选，如果还是找不到就报错
 *
 * spring中的Bean的创建的生命周期流程大致如下 :
 * UserService.class --> 推断构造方法来创建bean( 默认不写有无参构造spring也就自然调用无参构造来创建bean,如果自己重写构造则走重写的构造,如果自己重写多个构造却没有任何构造器上有指定@Autowired等注入注解则spring默认找无参
 * 构造找不到则报错 注入方式是从容器中先byType,type一样再byName去寻找bean来注入找不到就创建bean) -->生成bean对象(普通对象 beanDefinition 然后放入 beanDefinitionMap 中)
 * --> 用beanDefinition来开始生成单例bean对象 --> bean对象中的属性有其他bean对象的则依赖注入(构造器注入 set方法注入... 注入依旧是先byType再byName,且在这里解决循环依赖问题)
 * --> 调用初始化前方法(会调用aware回调 调用beanPostProcessor的前置方法) -->调用初始化方法(会调用程序员干预初始化的方法) -->调用初始化后方法(会调用beanPostProcessor的后置方法)
 * --> 生成代理对象(可能不生成直接是本身的bean看BeanPostProcessor的前置与后置方法的处理 spring中就是会扫描普通bean中的那些方法只要有加特定注解的则会生成一个代理对象且对这些方法进行增强，尽管很多方法都加了特定的注解但是
 * 仍然都是公用这一个代理对象保证单例，举例：比如@Transactional带事务的 @Async代理成异步的)
 * 拿@Transactional理解,在目标方法执行之前spring就会帮你关闭自动事务提交,然后帮你打开事务然后调用目标方法，目标方法执行没问题就提交有问题就回滚，spring中的很多其他的注解都是这样的实现原理。
 * --> 放入单例bean容器  --> 以后getBean是单例直接从单例bean容器中拿多例直接new后返回
 * 先byType再byName我们再标有@Bean注解的方法 传参 /@Component注解的类下 构造器 /  所需要的bean都是spring去容器的单例map里面找找到后注入给我们的
 *
 * 以下写的就是简单模拟以上的流程
 * 再上面的流程中有提到解决循环依赖问题，循环依赖是什么？
 * 关于spring的循环依赖：在创建bean的时候 A依赖B就去创建B与此同时B又依赖A就回去创建A那么死循环了，造成循环依赖,相当于容器中这两个bean都没有都需要创建的时候就才会有这个问题，
 * 若其中一方在容器中已经存在那么直接拿出来注入就好了，所以才会发现同样的代码偶尔启动有依赖注入报错偶尔启动没有的原因，因为类加载是spring加载的那就要看spring的加载顺序是什么了如果都没有再去依赖注入就有问题
 * 有一方有了再去注入自然就没问题了。可以参考如下的createBean理解
 * 如果容器中没有bean的时候就会去执行创建bean的逻辑，且创建bean的逻辑中会需要去解决依赖注入的问题，再解决依赖注入的问题的时候可能就会遇到循环依赖。循环依赖怎么解决？解决思路是什么？
 * 其实spring已经解决了循环依赖，spring是通过三级缓存解决的 其实就是三个map来打破循环依赖的 思路参考如下
 *  第一级缓存 ： singletonObjects      就是上面说到的用来存单例bean的map,经过了完整生命周期的bean对象map自然就是已经解决循环依赖问题的了,且getBean方法就是直接从这个容器中拿的
 *  第二级缓存 ： earlySingletonObjects 解决反复提前创建AOP的map,保证提前AOP的代理对象是单例的,避免不一致问题的
 *  第三级缓存 ： singletonFactories    放打破循环依赖第一步中通过构造函数创建出的普通bean,创建代理对象AOP对象需要使用到它
 * spring是怎么打破循环依赖的呢？
 * 当A在通过构造函数创建出普通bean对象的时候就先通过一个map(第三级缓存)先把这个普通bean存起来，后面A在依赖B时，当B去创建的时候发现又需要A且容器中没有的时候就去map中找找到就注入进来.
 * 那么就不会进入下一个创建bean循环了,那么B就被创建成功了,那么A自然也就能顺势往下走了，这样来看就好像打破了循环依赖。
 * 这样循环依赖确实是被打破了,但是存在一个问题。
 * bean在构造创建完成之后会通过 aware回调 初始化干预 跟beanPostProcessor 且beanPostProcessor可能会生成代理对象， 那么假如A类就是需要代理的那么A最后生成的是代理对象
 * 但A中的B对象中的循环依赖属性A是普通对象(因为B对象在上面就已经创建完成了通过依赖注入A的普通对象，但是实质上在循环依赖解决之后本身的A对象经过beanPostProcessor生成了代理 这个对象A对象跟B中的A不是一个对象) 这样会有问题。
 * 这个问题的解决思路是什么？ 能不能在A这个bean通过构造函数创建出普通bean对象之后就直接把这个普通bean生成代理对象然后 让其map中存的直接就是代理对象呢？只要能这样起步是B中注入的就是代理对象了。 --这个思路就叫提前AOP
 * 提前AOP看起来是可以解决的但是有没有什么问题呢？ 还是有点问题的,因为这个方法肯定是通用的
 * 一个bean如果没出现循环依赖,那么按道理应该是按照上面讲的流程在beanPostProcessor的后置方法执行完成之后才生成代理对象，只有出现循环依赖的bean才去提前aop。那么怎么区分什么时候生成代理对象呢即怎么判断bean是否出现了循环依赖呢？
 * 关于怎么判断bean是否出现了循环依赖可以在填充依赖对象的时候判断， A依赖B在生成B的时候判断B是不是也依赖A即可  spring是这样做的在createBean进来的时候先把当前正在实例化的bean的名字存在一个set中，比如此时是A要实例化
 * 那么这个set里面存的自然就是A的bean名字，然后A去注入B然后在B中B要注入自己A的依赖的时候只用比对是不是跟A的bean名字是否一样即可，一样就知道是产生了循环依赖了就进行提前AOP生成代理对象。
 * 然后在createBean方法的末尾只用把这个set数据清空即可。所以提前AOP是在这里提前。且提前AOP生成的代理对象放在第二级缓存earlySingletonObjects中放在这个map中的目的是假如A依赖了B又依赖了C且C依赖了A
 * 那么当A依赖注入完B后再去依赖注入C的时候发现又产生了循环依赖那么按照上面的逻辑C又会去提前生成A的代理对象,但是A是要单例的啊所以再生成代理对象就是两个A的代理对象了肯定就是不对的
 * 所以第二级缓存earlySingletonObjects就是用来解决这个问题的,先判断这个map中有无有的话直接注入即可不用再去提前生成AOP。且在A处理完依赖注入B跟C这两个属性的时候直接从二级缓存中去除自己的代理对象然后设置到一级缓存中去就OK了
 * 巧用全局数据结构协调bean的创建过程
 * 从这里来看只用了2个map 那么还有个map用来干嘛呢？
 * 我们第一步刚进来的时候创建出普通bean对象的时候就先通过一个map把这个普通bean存起来了这个map就是三级缓存singletonFactories,因为生成代理对象是需要依赖普通bean对象的。而且这个也是我们打破循环依赖的思路入口。
 * 所以它就是打破循环依赖的关键。
 * 所以其实如果bean不需要AOP的话(不生成代理对象的话)只用到两个map就解决了循环依赖问题,需要代理对象即用到了多一个map
 *
 * 关于一些其他的点,比如一些spring解决不了的循环依赖问题 与 @Lazy注解为什么能解决这种特殊的循环依赖问题。
 * 首先spring是基于构造去生成普通bean对象的如果在构造器里面就产生了循环依赖(A构造里面依赖B 去走B 结果B构造里面也依赖A),那么这时候连普通的bean对象都创建不出来肯定就报错了更别提后面那些了
 * 后面的循环依赖是spring通过三个map帮我们解决的,上面讲过了。
 * 怎么解决？ 在其中一个构造上面加上@Lazy注解即可 为什么？
 * 其实我们上面说的代理对象是基于beanPostProcessor产生的代理对象，但是spring中有时候需要处理一些特殊的比如@Async异步注解 这个异步注解也是用代理对象做的但是不是跟@Transactional即beanPostProcessor同一套
 * 所以有时候用这个@Async也会有spring无法解决的循环依赖问题,加个@Lazy注解即可代表懒加载，原理是它只是先创建一个代理对象，但是这个代理对象啥都做不了只是为了先注进去能表示有这个对象那么就不会走创建bean这一步就可以避免循环依赖
 * 那么这个代理对象啥时候才能真正的生成呢？在调用的时候才去真正的生成。这样就可以解决@Async报错跟构造函数说循环依赖问题了。
 *
 *
 * spring容器启动的时候会根据配置类或者配置文件去加载然后根据里面配置的扫描路径，去扫描生成BeanDefinition(我们上面的流程),加载完成后去判断BeanDefinition的那些bean哪些有@Configuration
 * 注解，有的就代表是配置类，即再去加载这个配置类里面的配置信息。
 *
 *
 */
public class JianxinApplicationContext {

    private Class configClass;

    //beanDefinition容器
    private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    //单例bean容器
    private ConcurrentHashMap<String,Object> singletonObjects = new ConcurrentHashMap<>();
    //bean初始化前/后程序员用户可进行自定义干预的list 代理对象beanList
    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();



    /**
     * 容器构造方法
     * 这个方法里面要做什么？
     * 通过我们自己对spring的基本理解这里是容器的构造函数也是启动方法那么要做的肯定有如下几件事
     * 1.根据配置扫描对应包把下面的bean都加载到容器
     * @param configClass
     */
    public JianxinApplicationContext(Class configClass) {
        this.configClass = configClass;
        //1.扫描包 jvm运行的是class字节码文件因此肯定得是拿到编译后的class的文件的路径,编译出来的class文件是在项目的out目录下
        //把这些class封装成beanDefinition后装载到beanDefinition容器
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            //配置类中扫描包路径  com.jianxin.service
            String path = componentScanAnnotation.value();
            //文件夹访问路径得把.替换成/  即com/jianxin/service
            path = path.replace(".","/");
            ClassLoader classLoader = JianxinApplicationContext.class.getClassLoader();
            //通过类加载器拿到当前类路径下的path的资源 即: C:\Users\dell\Desktop\jianxin-spring-source\out\production\jianxin-spring-source\com\jianxin\service
            URL resource = classLoader.getResource(path);
            //拿到 C:\Users\dell\Desktop\jianxin-spring-source\out\production\jianxin-spring-source\com\jianxin\service 这个文件
            File file = new File( resource.getFile() );
//            System.out.println(file);
            if (file.isDirectory()) {
                //遍历目录下的所有文件
                File[] files = file.listFiles();
                for (File f : files) {
                    //拿到文件的绝对路径
                    String fileName = f.getAbsolutePath();
//                    System.out.println(fileName);
                    //我们只要.class文件
                    if (fileName.endsWith(".class")) {
                        //通过反射判断该文件是不是一个bean
                        //通过绝对路径截取出 xx.xx.xx 这种形式当作loadClass的参数去加载类
                        //比如：C:\Users\dell\Desktop\jianxin-spring-source\out\production\jianxin-spring-source\com\jianxin\service\AppConfig.class 变成 com.jianxin.service.AppConfig
                        String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
                        String replaceClassName = className.replace("\\", ".");
                        try {
//                            System.out.println(replaceClassName);
                            Class<?> clazz = classLoader.loadClass(replaceClassName);
                            if (clazz.isAnnotationPresent(Component.class)) {

                                //instanceof 只能判断对象是否是哪个类  class判断是否是哪个类得用如下这个
                                //当发现该类是beanPostProcessor的实现类即装入beanPostProcessorList
                                //这样在创建bean的前后可以提供给程序员让它们做一些自己的事
                                if(BeanPostProcessor.class.isAssignableFrom(clazz)){
                                    BeanPostProcessor instance = (BeanPostProcessor) clazz.newInstance();
                                    beanPostProcessorList.add(instance);
                                }

                                Component componentAnnotation = clazz.getAnnotation(Component.class);
                                //beanName作为容器key
                                String beanName = componentAnnotation.value();

                                //Component属性value不写的情况则默认用类名 驼峰命名法方式
                                if (beanName.equals("")) {
                                    beanName = Introspector.decapitalize(clazz.getSimpleName());
                                }


                                //加载生成BeanDefinition后放到容器
                                BeanDefinition beanDefinition = new BeanDefinition();
                                //设置bean对象本身
                                beanDefinition.setType(clazz);
                                //单例or多例
                                if (clazz.isAnnotationPresent(Scope.class)) {
                                    Scope scopeAnnotation = clazz.getAnnotation(Scope.class);
                                    String scope = scopeAnnotation.value();
                                    if ("prototype".equals(scope)){
                                        //多例
                                        beanDefinition.setScope("prototype");
                                    }else {
                                        //采用默认值 - 即单例
                                        beanDefinition.setScope(scope);
                                    }
                                }else {
                                    //不带Scope注解默认也是单例
                                    beanDefinition.setScope("singleton");
                                }
                                //装入容器
                                beanDefinitionMap.put(beanName,beanDefinition);
                            }
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        } catch (InstantiationException e) {
                            throw new RuntimeException(e);
                        } catch (IllegalAccessException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }

        //2.根据配置构造beanDefinition容器中的所有单例bean
        for (String beanName : beanDefinitionMap.keySet()) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            if (beanDefinition.getScope().equals("singleton")) {
                Object bean = createBean(beanName, beanDefinition);
                //保存至单例bean容器
                singletonObjects.put(beanName,bean);
            }
        }


    }

    /**
     * 创建bean对象 - 初始化bean
     * @param beanName
     * @return
     */
    private Object createBean(String beanName,BeanDefinition beanDefinition){
        Class clazz = beanDefinition.getType();
        try {
            //通过无参构造去生成bean实例
            Object instance = clazz.getConstructor().newInstance();
            //简单版处理依赖注入(属性填充) - 根据属性名找到bean注入
            //todo 但这里存在循环依赖问题
            for (Field f : clazz.getDeclaredFields()) {
                if (f.isAnnotationPresent(Autowired.class)) {
                    //允许通过反射操作private属性
                    f.setAccessible(true);
                    //给instance实例的f属性赋值
                    f.set(instance,getBean(f.getName()));
                }
            }

            //aware回调 - beanNameAware
            if (instance instanceof BeanNameAware) {
                ((BeanNameAware)instance).setBeanName(beanName);
            }

            //初始化前 - 调用 beanPostProcessor 的 before方法
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
               instance = beanPostProcessor.postProcessBeforeInitialization(beanName, instance);
            }

            //干预初始化 - InitializingBean
            if (instance instanceof InitializingBean) {
                ((InitializingBean)instance).afterPropertiesSet();
            }

            //初始化后 - 调用 beanPostProcessor 的 after方法
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                instance = beanPostProcessor.postProcessAfterInitialization(beanName,instance);
            }


            return instance;
        } catch (Exception e) {
            throw new RuntimeException("bean non-param Constructor not exist");
        }
    }


    /**
     * 根据名字获取bean
     * spring中是用来包含bean对象本身还有bean的一些属性的BeanDefinition来承接的
     * 因为bean有单例有多例(单例：从容器中取出返回 多例：新建一个bean返回)且可能还有其他的属性
     * @param beanName
     * @return
     */
    public Object getBean(String beanName) {
        //从beanDefinition容器中取出beanDefinition
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if(beanDefinition==null){
            throw new RuntimeException("bean no exist!");
        }
        String scope = beanDefinition.getScope();
        if(scope.equals("singleton")){
            //单例 - 单例容器中 有则返回 没有则创建然后返回
            Object bean = singletonObjects.get(beanName);
            if(bean == null){
                bean = createBean(beanName,beanDefinition);
                singletonObjects.put(beanName,bean);
            }
            return bean;
        }else {
            //多例
            return createBean(beanName,beanDefinition);
        }
    }

}
